package web;


import au.edu.swin.bank.ejb.session.stateless.AccountRemote;
import au.edu.swin.bank.ejb.session.stateless.CustomerRemote;
import au.edu.swin.bank.ejb.session.stateless.TransactionRemote;
import au.edu.swin.bank.persistence.entity.BankTransactionDTO;
import au.edu.swin.bank.persistence.entity.BillDTO;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * @author scott
 */

@WebServlet(name = "SwinBankServlet", urlPatterns = {"/MainServlet", "/login", "/main", "/transferFunds", "/transferFundsSuccess", "/processTransfer", "/selectBill", "/payBill", "/processBillPayment", "/logout", "/processLogin", "/transactionHistory", "/selectDates"})
public class SwinBankServlet extends HttpServlet
{
   @EJB private CustomerRemote customerBean;
   @EJB private AccountRemote accountBean;
   @EJB private TransactionRemote transactionBean;

    /**
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        //Get our user's session (create if none exists)
        HttpSession session = request.getSession(true);
        System.out.println("Getting: " + request.getRequestURI() + " cust: " + session.getAttribute("customerId"));
        if(session.getAttribute("accountHistoryId") == null )
            session.setAttribute("mainMessage", null);
        session.setAttribute("accountHistoryId", null);

        if(session.getAttribute("transferFundsMessageCheck") == null)
            session.setAttribute("transferFundsMessage", null);
        session.setAttribute("transferFundsMessageCheck", null);

        try {

            customerBean.forceEvict();
            accountBean.forceEvict();
            transactionBean.forceEvict();

            if(session.getAttribute("customerId") == null) // if the user is not logged in yet, direct them to do so.
            {
                if(request.getRequestURI().endsWith("/processLogin")) //If a login action is meant to be performed, do so
                {
                    if(processLogin(request, response))
                    {
                        //if the login succeeded, forward the user to the main page
                        session.setAttribute("loginMessage", null);
                        response.sendRedirect("/SwinBank-war/main");
                        return;
                    }
                    else
                    {
                        //if the login failed, redirect them back to the login page (with an error message)
                        session.setAttribute("loginMessage", "Login failed. Please try again.");
                        response.sendRedirect("/SwinBank-war/login");
                    }
                }
                else if(!request.getRequestURI().endsWith("/login"))
                {
                    //if the user is not logged in, but is trying to access a non-login page, redirect them!
                    response.sendRedirect("/SwinBank-war/login");
                }
            }
            else //if the user is already logged in...
            {
                setAccountsList(session); //done here to ensure the latest updates to account balances are shown every time a page is accessed
                
                if(request.getRequestURI().endsWith("/main"))
                {
                    getServletContext().getRequestDispatcher("/main.jsp").forward(request, response);
                }
                else if(request.getRequestURI().endsWith("/transferFunds"))
                {
                    getServletContext().getRequestDispatcher("/transferFunds.jsp").forward(request, response);
                }
                else if(request.getRequestURI().endsWith("/transferFundsSuccess"))
                {
                    getServletContext().getRequestDispatcher("/transferFundsSuccess.jsp").forward(request, response);
                }
                else if(request.getRequestURI().endsWith("/processTransfer"))
                {
                    //if the transfer succeeds, forward the user to the transfer success page
                    //if the transfer fails, redirect the user back to the transfer page (with an error message)
                    if (processTransfer(request, response))
                    {
                        response.sendRedirect("/SwinBank-war/transferFundsSuccess");
                    }
                    else
                    {
                        session.setAttribute("transferFundsMessage", "Funds transfer failed! Please ensure that all transfer details are correct and try again.");
                        session.setAttribute("transferFundsMessageCheck", "");
                        response.sendRedirect("/SwinBank-war/transferFunds");
                    }
                }
                else if(request.getRequestURI().endsWith("/selectDates"))
                {
                    try
                    {
                        if (request.getParameter("accountHistoryId") == null)
                            throw new Exception("No account id specified.");
                        else
                        {
                            BigInteger accountHistoryId = new BigInteger(request.getParameter("accountHistoryId"));
                            session.setAttribute("accountHistoryId", accountHistoryId);

                            getServletContext().getRequestDispatcher("/selectDates.jsp").forward(request, response);
                        }
                    }
                    catch (Exception e)
                    {
                        session.setAttribute("mainMessage", "Invalid account id!");
                        response.sendRedirect("/SwinBank-war/main");
                        return;
                    }
                }
                else if(request.getRequestURI().endsWith("/transactionHistory"))
                {
                    if (request.getParameter("startDay") == null
                        || request.getParameter("startMonth") == null
                        || request.getParameter("startYear") == null
                        || request.getParameter("endDay") == null
                        || request.getParameter("endMonth") == null
                        || request.getParameter("endYear") == null
                       )
                    {
                        session.setAttribute("mainMessage", "Invalid date(s) specified!");
                        response.sendRedirect("/SwinBank-war/main");
                        return;
                    }

                    if (request.getParameter("accountHistoryId") == null)
                    {
                        session.setAttribute("mainMessage", "Invalid account id!");
                        response.sendRedirect("/SwinBank-war/main");
                        return;
                    }
                    else
                    {
                        BigInteger i = new BigInteger(request.getParameter("accountHistoryId"));
                        if(!customerBean.getAccountIds(Integer.parseInt((String)session.getAttribute("customerId"))).contains(i))
                        {
                            session.setAttribute("mainMessage", "Invalid transaction history address!");
                            session.setAttribute("accountHistoryId", i);
                            response.sendRedirect("/SwinBank-war/main");
                            return;
                        }
                        else
                        {
                            BigInteger startDate;
                            BigInteger endDate;
                            
                            try
                            {

                                int startDay = Integer.valueOf(request.getParameter("startDay"));
                                int startMonth = Integer.valueOf(request.getParameter("startMonth"));
                                int startYear = Integer.valueOf(request.getParameter("startYear"));
                                
                                int endDay = Integer.valueOf(request.getParameter("endDay"));
                                int endMonth = Integer.valueOf(request.getParameter("endMonth"));
                                int endYear = Integer.valueOf(request.getParameter("endYear"));

                                DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
                                Date now = new Date();


                                startDate = BigInteger.valueOf(dateFormat.parse(startDay + "-" + startMonth + "-" + startYear + " 00:00:00").getTime());
                                endDate = BigInteger.valueOf(dateFormat.parse(endDay + "-" + endMonth + "-" + endYear + " " + now.getHours() + ":" + now.getMinutes() + ":" + now.getSeconds()).getTime());

                                //Check the date range is valid
                                if (!isDateRangeValid(startDate, endDate)) 
                                {
                                    throw new Exception("Invalid date range encountered.");
                                }
                            }
                            catch (Exception e)
                            {
                                response.sendRedirect("/SwinBank-war/selectDates");
                                return;
                            }

                            List<BankTransactionDTO> transactionList = new LinkedList<BankTransactionDTO>();
                            for (BankTransactionDTO b : transactionBean.getTransactions(i, startDate, endDate))
                            {
                                transactionList.add(b);
                            }
                            session.setAttribute("history", transactionList);
                            getServletContext().getRequestDispatcher("/transactionHistory.jsp").forward(request, response);
                        }
                    }
                }
                else if(request.getRequestURI().endsWith("/selectBill"))
                {
                    Collection<BillDTO> unpaidBills = customerBean.getBills(Integer.parseInt((String)session.getAttribute("customerId")), true);
                    session.setAttribute("unpaidBills", unpaidBills);
                    session.setAttribute("selectedBill", null);

                    getServletContext().getRequestDispatcher("/selectBill.jsp").forward(request, response);
                }
                else if(request.getRequestURI().endsWith("/payBill"))
                {
                    try
                    {
                        //check whether the specified bill to pay exists in the customer's unpaid bills list
                        BigInteger selectedBillId = new BigInteger(request.getParameter("selectedBillId"));
                        Collection<BillDTO> unpaidBills = (Collection<BillDTO>)session.getAttribute("unpaidBills");
                        for (BillDTO bill : unpaidBills)
                        {
                            if (bill.getBillId().equals(selectedBillId))
                            {
                                //if the bill is unpaid, then forward the user to the bill payment page
                                session.setAttribute("selectedBill", bill);
                                getServletContext().getRequestDispatcher("/payBill.jsp").forward(request, response);
                                return;
                            }
                        }
                        
                        throw new Exception("Selected bill is not in the customer's unpaid bills list.");
                    }
                    catch (Exception e)
                    {
                        response.sendRedirect("/SwinBank-war/selectBill");  //TODO: show error message
                    }
                }
                else if(request.getRequestURI().endsWith("/processBillPayment"))
                {
                    //if the bill payment succeeds, forward the user to the bill payment success page
                    //if the bill payment fails, redirect the user back to the bill payment page (with an error message)
                    if (processBillPayment(request, response))
                    {
                        session.setAttribute("billPaymentMessage", null);
                        response.sendRedirect("/SwinBank-war/billPaymentSuccess.jsp");
                    }
                    else
                    {
                        BillDTO bill = (BillDTO) session.getAttribute("selectedBill");
                        BigInteger billId = bill.getBillId();
                        session.setAttribute("billPaymentMessage", "Bill payment failed! Please ensure that all payment details are correct and try again.");
                        response.sendRedirect("/SwinBank-war/payBill?selectedBillId=" + billId);
                    }
                }
                else if(request.getRequestURI().endsWith("/processLogin"))
                {
                   response.sendRedirect("/SwinBank-war/logout");
                }
                else if(request.getRequestURI().endsWith("/login"))
                {
                    response.sendRedirect("/SwinBank-war/logout");
                }
            }

            if(request.getRequestURI().endsWith("/logout"))
            {
                //Handle the logout operation (clear all session objects)
                session.setAttribute("customerId", null);
                session.setAttribute("accounts", null);
                session.setAttribute("unpaidBills", null);
                session.setAttribute("selectedBill", null);
                session.setAttribute("loginMessage", null);
                session.setAttribute("transferFundsMessage", null);
                session.setAttribute("billPaymentMessage", null);

                transactionBean.setCustId(null);
                
                try
                {
                    response.sendRedirect("/SwinBank-war/login");
                } catch(IllegalStateException e){
                    System.err.println("Dropping exception: "+ e.toString());
                    //ignore it, its not of importance (we think)
                }
            }
            if(request.getRequestURI().endsWith("/login")){
                try{
                    getServletContext().getRequestDispatcher("/login.jsp").forward(request, response);
                }catch(Exception e){
                    System.err.println("Dropping exception: "+ e.toString());
                    //ignore it, its not of importance (we think)
                }
            }

            } finally {
            out.close();
        }
    }
    /**
     * Processes a users login by calling the customerBean and checking the username and password (comparing the user input to the db).
     * @param request servlet request
     * @param response servlet response
     * @return true on successful login.
     */
    protected boolean processLogin(HttpServletRequest request, HttpServletResponse response)
    {
        try
        {
            HttpSession session = request.getSession(true);
            Integer formUsername = Integer.parseInt(request.getParameter("username"));
            Integer formPassword = Integer.parseInt(request.getParameter("password"));
            
            System.out.println("Trying to login as: " + formUsername + " with password " + formPassword);
            System.err.println("Trying to login as: " + formUsername + " with password " + formPassword);


            boolean loginSuccessful = customerBean.customerLogin(formUsername, formPassword);
            if(loginSuccessful)
            {
                //if login successful, record our user's customerId for later use, and login tracking
                session.setAttribute("customerId", request.getParameter("username"));
                transactionBean.setCustId(formUsername);
                return true;
            }
            else
            {
                return false;
            }
        } 
        catch (Exception e)
        {
            return false;
        }
    }

    /**
     * Processes a funds transfer by calling the transactionBean and sending the money from the source account to the destination account.
     * @param request servlet request
     * @param response servlet response
     * @return true on successful transfer.
     */
    protected boolean processTransfer(HttpServletRequest request, HttpServletResponse response)
    {
        try
        {
            BigInteger fromAccountId = new BigInteger(request.getParameter("fromAccountId"));
            BigInteger toAccountId = new BigInteger(request.getParameter("toAccountId"));
            double amount = Double.valueOf(request.getParameter("amount"));
            String description = request.getParameter("description");
            
            System.out.println("Trying to transfer $" + amount + " from account " + fromAccountId + " to account " + toAccountId + " with description '" + description + "'.");
            System.err.println("Trying to transfer $" + amount + " from account " + fromAccountId + " to account " + toAccountId + " with description '" + description + "'.");

            return transactionBean.transferFunds(amount, fromAccountId, toAccountId, description);
        } 
        catch (Exception e)
        {
            e.printStackTrace();
            return false;
        }
    }
    
    /**
     * Processes a bill payment by calling the transactionBean and sending the money from the source account to the biller's account.
     * @param request servlet request
     * @param response servlet response
     * @return true on successful payment.
     */
    protected boolean processBillPayment(HttpServletRequest request, HttpServletResponse response)
    {
        try
        {
            HttpSession session = request.getSession(true);
            BigInteger fromAccountId = new BigInteger(request.getParameter("fromAccountId"));
            BillDTO bill = (BillDTO) session.getAttribute("selectedBill");
            BigInteger billId = bill.getBillId();
            double amount = Double.valueOf(request.getParameter("amount"));
            String description = request.getParameter("description");

            System.out.println("Trying to pay bill " + billId + " with $" + amount + " from account " + fromAccountId + "'.");
            System.err.println("Trying to pay bill " + billId + " with $" + amount + " from account " + fromAccountId + "'.");

            return transactionBean.payBill(amount, fromAccountId, billId, description);
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Sets the accounts list used in the session.
     *
     * @param session the session
     */
    protected void setAccountsList(HttpSession session){
        Collection<BigInteger> accList = customerBean.getAccountIds(Integer.parseInt((String)session.getAttribute("customerId")));
        Map<String, String> accBalList = new HashMap<String, String>();
        for(BigInteger i : accList)
            accBalList.put(i.toString(),Double.toString(accountBean.getBalance(i)));
        if(accBalList.isEmpty())
            accBalList.put("No accounts exist", "for this customer.");
        session.setAttribute("accounts", accBalList);
    }

     /**
     * Checks if the supplied date range is valid.
     * The start date must be before the end date and neither date can be in the future.
     * @param aStartDate The start date
     * @param aEndDate The end date
     * @return True if valid, otherwise false
     */
    private static boolean isDateRangeValid(BigInteger aStartDate, BigInteger aEndDate)
    {
        Date date = new Date();
        if (aStartDate.compareTo(aEndDate) < 0) {
            if (aEndDate.compareTo(BigInteger.valueOf(date.getTime())) < 0) {
                return true;
            }
        }
        return false;
    }
    
    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /** 
     * Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    } 

    /** 
     * Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        processRequest(request, response);
    }

    /** 
     * Returns a short description of the servlet.
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>

}
